#include <iostream>
#include <csignal>
#include <vector>
#include <sstream>
#include <ctime>
#include <map>
#include <ncurses.h>

#include "Environment/EnvironmentFactory.h"
#include "Testing.h"

#include "NCurses/NCurses.h"
#include "NCurses/Window/Status/NStatusWindow.h"
#include "NCurses/Window/Dialog/NDialogWindow.h"
#include "NCurses/Window/Menu/NMenuWindow.h"

#include "Crossover/SinglePoint/SinglePointCrossover.h"
#include "Solution/PermutationEncoded/PermutationEncodedSolution.h"
#include "Selection/Tournament/TournamentSelection.h"
#include "Selection/Roulette/RouletteSelection.h"
#include "Selection/Random/RandomSelection.h"
#include "Selection/Rank/RankSelection.h"
#include "Selection/SelectionFactory.h"
#include "Random/MyRandom.h"
#include "Framework/Framework.h"
#include "Worker/Crossover/Local/LocalCrossoverWorker.h"
#include "Worker/EvaluateFitness/Local/LocalEvaluateFitnessWorker.h"
#include "zthread/Thread.h"

bool developerIsTrevor = true;

void getUserInput(Environment* &, Crossover* &, Selection* &, map<string, double> &);
void outputBestSolutions(vector<Solution*>, Environment* &);
void outputAvgFitnessHistory(list<double> &, unsigned int);
void outputTopFitnessHistory(list<double> &, unsigned int);
void signalHandler(int);

int main(void)
{
    MyRandom::init((unsigned int) time(0));
    
    if (developerIsTrevor) {
        Testing::doTest();
        cout << "Dying as per developer request!" << endl;
        exit(0);
    }

    Environment* simulation;
    Crossover* crosser;
    Selection* selector;
    map<string, double> options;
    
    getUserInput(simulation, crosser, selector, options);
    Framework frame(simulation, crosser, selector, options);

    LocalEvaluateFitnessWorker *evaluateFitnessWorker1 = new LocalEvaluateFitnessWorker();
    LocalEvaluateFitnessWorker *evaluateFitnessWorker2 = new LocalEvaluateFitnessWorker();
    LocalEvaluateFitnessWorker *evaluateFitnessWorker3 = new LocalEvaluateFitnessWorker();    
    LocalCrossoverWorker *crossoverWorker1 = new LocalCrossoverWorker();
    LocalCrossoverWorker *crossoverWorker2 = new LocalCrossoverWorker();
    LocalCrossoverWorker *crossoverWorker3 = new LocalCrossoverWorker();
    
    frame.registerWorker(evaluateFitnessWorker1);
    frame.registerWorker(evaluateFitnessWorker2);
    frame.registerWorker(evaluateFitnessWorker3);
    frame.registerWorker(crossoverWorker1);
    frame.registerWorker(crossoverWorker2);
    frame.registerWorker(crossoverWorker3);

    ZThread::Thread *evaluateFitnessWorkerThread1 = new ZThread::Thread(evaluateFitnessWorker1);
    ZThread::Thread *evaluateFitnessWorkerThread2 = new ZThread::Thread(evaluateFitnessWorker2);
    ZThread::Thread *evaluateFitnessWorkerThread3 = new ZThread::Thread(evaluateFitnessWorker3);
    ZThread::Thread *crossoverWorkerThread1 = new ZThread::Thread(crossoverWorker1);
    ZThread::Thread *crossoverWorkerThread2 = new ZThread::Thread(crossoverWorker2);
    ZThread::Thread *crossoverWorkerThread3 = new ZThread::Thread(crossoverWorker3);    
    
    time_t startTime = time(NULL);
    int seconds;
	cout << "How long (in seconds) do you want to run? ";
	cin >> seconds;
    
    time_t stopTime = startTime + seconds;
    
    frame.start();
    
    double avgFitness;
    double topFitness;
    unsigned int genCount;
    
    NCurses::init(true, true, true);
    NStatusWindow window;
    window.setEnvironment(simulation->getName());
    window.show();
    
    while (time(NULL) < stopTime)
    {
        frame.getBriefFitnessHistory(avgFitness, topFitness, genCount);
        window.setGenerations(genCount);
        window.setTopFitness(topFitness);
        window.setAvgFitness(avgFitness);
        window.setTime(stopTime - time(NULL));
        sleep(1);
    }
    
    NCurses::end();
		
	frame.stop();
    
    frame.unregisterWorker(evaluateFitnessWorker1);
    frame.unregisterWorker(evaluateFitnessWorker2);
    frame.unregisterWorker(evaluateFitnessWorker3);
    frame.unregisterWorker(crossoverWorker1);
    frame.unregisterWorker(crossoverWorker2);
    frame.unregisterWorker(crossoverWorker3);
    
    evaluateFitnessWorkerThread2->wait();
    evaluateFitnessWorkerThread1->wait();
    evaluateFitnessWorkerThread3->wait();
    crossoverWorkerThread1->wait();
    crossoverWorkerThread2->wait();
    crossoverWorkerThread3->wait();
    
    delete evaluateFitnessWorkerThread2;
    delete evaluateFitnessWorkerThread1;
    delete evaluateFitnessWorkerThread3;
    delete crossoverWorkerThread1;
    delete crossoverWorkerThread2;
    delete crossoverWorkerThread3;
    
    outputBestSolutions(frame.getBestSolutions(), simulation);
    list<double> averageFitnessHistory;
    list<double> maxFitnessHistory;
    unsigned int generations;
    frame.getFitnessHistory(averageFitnessHistory, maxFitnessHistory, generations);
    outputAvgFitnessHistory(averageFitnessHistory, generations);    
    outputTopFitnessHistory(maxFitnessHistory, generations);
    
    delete simulation;
    delete crosser;
    delete selector;
    
    cout << "Simulation complete." << endl;
    
    return 0;
}

/**
 * Retrieves the user parameters for running the framework.
 * Also, instantiates the objects needed to run the framework.
 */
void getUserInput(Environment* &simulation, Crossover* &crosser, 
                  Selection* &selector, map<string, double> &options)
{
    SelectionFactory selectionFactory;
    
    if (developerIsTrevor) 
    {
        Solution::setMutationRate(.0025); // = .25% someone said it was a good number to use
        options["topResultsSize"] = 10;
        options["populationSize"] = 1000;
        options["historySize"] = 10; 
        
        simulation = new JobShopSchedulingEnvironment<PermutationEncodedSolution>();
        simulation->configure();
        crosser = simulation->configureCrossover();
    }
    else 
    {
        Solution::setMutationRate(.01); 
        
        NCurses::init(true, true, true);
        NMenuWindow menu;
        menu.setChoices(EnvironmentFactory::getEnvironmentNames());
        menu.setShowNumbers(true);
        menu.center();
        
        
        int choice = menu.getChoice();

        NDialogWindow dialog;
        dialog.setPrompt("How large do you want the population to be? ");
        dialog.center();
        string population = dialog.getInput();
        
	   std::istringstream istream(population);
       int x = 0;
       istream >> x;
        
        dialog.clear();
        
        NCurses::end();
        
//        cout << "popsize: " << options["populationSize"] << endl;
        cout << "string: " << population << endl;
        cout << "x: " << x << endl;
        
        exit(0);
        
        options["topResultsSize"] = 10;
        //cout << "How large a population do you want to use? ";
        //cin >> options["populationSize"];
        options["historySize"] = 10;
        
        simulation = EnvironmentFactory::generateEnvironment(choice);
        simulation->configure();

        crosser = simulation->configureCrossover();
    }
    
    selector = selectionFactory.generateSelection();
}

void outputBestSolutions(vector<Solution*> best, Environment* &simulation) {
    cout << "the best solutions were:" << endl;
    
    for (unsigned int x = 0; x < best.size(); x++) 
    {
        cout << "[" << x + 1<< "] ";
        cout.width(10);
        cout << best[x]->getFitness();
        cout.width(0);
        cout << ": " << simulation->formatSolution(best[x]) << endl;
        delete best[x];
    }
}

void outputAvgFitnessHistory(list<double> &averageFitnessHistory, unsigned int generations) 
{
    cout << "Average fitness history:" << endl;
    int x = 0;    
    list<double>::iterator it = averageFitnessHistory.begin();
    
    while(it != averageFitnessHistory.end() && (generations - x) >= 0)
    {
        ++x;
        cout << "[generation " << generations - x << "]: ";
        cout.width(10);
        cout << *it;
        cout.width(0);
        cout << endl;
        ++it;
    }
}

void outputTopFitnessHistory(list<double> &maxFitnessHistory, unsigned int generations) {
    cout << "Maximum fitness history:" << endl;
    int x = 0;
    list<double>::iterator it = maxFitnessHistory.begin();
    
    while(it != maxFitnessHistory.end() && (generations - x) >= 0)
    {
        ++x;
        cout << "[generation " << generations - x << "]: ";
        cout.width(10);
        cout << *it;
        cout.width(0);
        cout << endl;
        ++it;
    }
}
